﻿// <copyright file="FileWriter.cs" company="NorthCoast NERO">
//     NorthCoast NERO. All rights reserved.
// </copyright>
// <author>Eric Kociecki</author>
namespace NcnPlayerCheckIn
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Xml;

    /// <summary>
    /// Code for writing check-in log to a file.
    /// </summary>
    public static class FileWriter
    {
        /// <summary>
        /// Read in a list of players from an XML file.
        /// </summary>
        /// <returns>A list of Player objects.  This may be an empty list if the file does not exist or has no valid player data.</returns>
        public static Collection<Player> ReadFile()
        {
            string filePath = ValidateFilePath();
            Collection<Player> playerList = new Collection<Player>(new List<Player>());

            // if the file exists, read it, otherwise just return the empty playerList
            if (File.Exists(filePath))
            {
                try
                {
                    XmlReader reader = CreateAndConfigureXmlReader(filePath);

                    while (reader.ReadToFollowing("player"))
                    {
                        ReadPlayer(reader.ReadSubtree(), playerList);
                    }

                    reader.Close();
                }
                catch (Exception ex)
                {
                    // We could handle each exception type separately for logging purposes, to prompt the user for a fix, or to retry.  But in all cases, it is acceptable (not necessarily desirable) to continue on with a new blank data set.
                    ex.ToString();
                }
            }

            return playerList;
        }

        /// <summary>
        /// Write a list of players to an XML file to be opened by this program later.
        /// </summary>
        /// <param name="playerList">The list of player to be written to a file.</param>
        public static void WriteFile(Collection<Player> playerList)
        {
            string filePath = ValidateFilePath();

            if (playerList.Count > 0)
            {
                XmlTextWriter writer = new XmlTextWriter(filePath, System.Text.UTF8Encoding.UTF8);

                writer.Formatting = Formatting.Indented;
                writer.WriteStartDocument();
                writer.WriteStartElement("players");

                foreach (Player player in playerList)
                {
                    writer.WriteStartElement("player");
                    writer.WriteAttributeString("name", player.PlayerName);
                    
                    writer.WriteStartElement("characterName");
                    writer.WriteValue(player.CharacterName);
                    writer.WriteEndElement();
                    
                    writer.WriteStartElement("homeChapter");
                    writer.WriteValue(player.HomeChapter);
                    writer.WriteEndElement();

                    writer.WriteStartElement("buildTotal");
                    writer.WriteValue(player.BuildTotal);
                    writer.WriteEndElement();

                    writer.WriteStartElement("pcDaysCount");
                    writer.WriteValue(player.PcDaysCount);
                    writer.WriteEndElement();

                    writer.WriteStartElement("npcDaysCount");
                    writer.WriteValue(player.NpcDaysCount);
                    writer.WriteEndElement();

                    writer.WriteStartElement("eventFee");
                    writer.WriteValue(player.EventFee);
                    writer.WriteEndElement();

                    bool hasWrittenStart = false;
                    foreach (DiscountType dt in player.Discounts)
                    {
                        if (!hasWrittenStart)
                        {
                            writer.WriteStartElement("discounts");
                            hasWrittenStart = true;
                        }

                        writer.WriteStartElement("discount");

                        writer.WriteStartElement("name");
                        writer.WriteValue(dt.Name);
                        writer.WriteEndElement();

                        writer.WriteStartElement("value");
                        writer.WriteValue(dt.Value);
                        writer.WriteEndElement();

                        writer.WriteStartElement("isStackable");
                        writer.WriteValue(dt.IsStackable);
                        writer.WriteEndElement();

                        writer.WriteStartElement("id");
                        writer.WriteValue(dt.Id);
                        writer.WriteEndElement();

                        writer.WriteEndElement();
                    }

                    if (hasWrittenStart)
                    {
                        writer.WriteEndElement();
                    }

                    writer.WriteStartElement("notes");
                    writer.WriteValue(player.Notes);
                    writer.WriteEndElement();

                    writer.WriteStartElement("hasCheckedOut");
                    writer.WriteValue(player.HasCheckedOut);
                    writer.WriteEndElement();

                    writer.WriteEndElement();
                }

                writer.WriteEndElement();
                writer.WriteEndDocument();
                writer.Close();
            }
        }

        /// <summary>
        /// Creates, configures, and returns a new XmlReader object using the supplied file path.
        /// </summary>
        /// <param name="filePath">The name and path of the file XmlReader will read.</param>
        /// <returns>An XmlReader object pointing to the given file.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when filePath is null.</exception>
        /// <exception cref="System.IO.FileNotFoundException">Thrown when filePath is pointing to a non-existant or non-accessible file.</exception>
        /// <exception cref="System.UriFormatException">Thrown when filePath has invalid syntax.</exception>
        private static XmlReader CreateAndConfigureXmlReader(string filePath)
        {
            // configure XmlReader settings
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreComments = true;
            settings.IgnoreWhitespace = true;

            // create XmlReader
            XmlReader reader;
            try
            {
                reader = XmlReader.Create(filePath, settings);
            }
            catch (Exception ex)
            {
                if (ex is ArgumentNullException || ex is UriFormatException)
                {
                    // The calling method should be validating the path, so check that code.
                    ex.ToString(); // log this (once we add logging)
                }
                else if (ex is FileNotFoundException)
                {
                    // We should be checking for File.Exists(filePath) before this method.  If this is being done and we still hit this exception, it means the file was made inaccesible since checking Exists().  Rethrow the exception and the calling method should move on with a default empty data set.
                    ex.ToString(); // log this (once we add logging)
                }

                // rethrow exceptions
                throw;
            }

            return reader;
        }

        /// <summary>
        /// This will accept a file name and path, validate it, correct it if necessary, and return it.  For now, it just returns a default file name.
        /// </summary>
        /// <returns>The validated file name and path to open.</returns>
        private static string ValidateFilePath()
        {
            string filePath = "test.xml";
            
            return filePath;
        }

        /// <summary>
        /// Read in a single player from the given XML Reader object.  Store the player data in the given playerList.
        /// </summary>
        /// <param name="reader">The XML data to read from.</param>
        /// <param name="playerList">The list of players to add the new player to.</param>
        private static void ReadPlayer(XmlReader reader, Collection<Player> playerList)
        {
            Player newPlayer = new Player();

            reader.Read();

            newPlayer.PlayerName = reader.GetAttribute("name");

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    ReadPlayerDataField(reader.ReadSubtree(), newPlayer);
                }
            }

            playerList.Add(newPlayer);
        }

        /// <summary>
        /// Reads a single subtree of player data in the XML tree.  This data is then stored in the given Player object.
        /// </summary>
        /// <param name="reader">The subtree of XML data to read.</param>
        /// <param name="newPlayer">The Player object to store the data in.</param>
        private static void ReadPlayerDataField(XmlReader reader, Player newPlayer)
        {
            reader.Read();

            switch (reader.Name)
            {
                case "characterName":
                    newPlayer.CharacterName = reader.ReadElementContentAsString();
                    break;
                case "playerName":
                    newPlayer.PlayerName = reader.ReadElementContentAsString();
                    break;
                case "homeChapter":
                    newPlayer.HomeChapter = reader.ReadElementContentAsString();
                    break;
                case "buildTotal":
                    newPlayer.BuildTotal = Convert.ToInt32(reader.ReadElementContentAsString(), CultureInfo.CurrentCulture);
                    break;
                case "pcDaysCount":
                    newPlayer.PcDaysCount = Convert.ToInt32(reader.ReadElementContentAsString(), CultureInfo.CurrentCulture);
                    break;
                case "npcDaysCount":
                    newPlayer.NpcDaysCount = Convert.ToInt32(reader.ReadElementContentAsString(), CultureInfo.CurrentCulture);
                    break;
                case "eventFee":
                    newPlayer.EventFee = Convert.ToInt32(reader.ReadElementContentAsString(), CultureInfo.CurrentCulture);
                    break;
                case "discounts":
                    ReadDiscounts(reader.ReadSubtree(), newPlayer);
                    break;
                case "notes":
                    newPlayer.Notes = reader.ReadElementContentAsString();
                    break;
                case "hasCheckedOut":
                    newPlayer.HasCheckedOut = reader.ReadElementContentAsBoolean();
                    break;
                default:
                    reader.ReadElementContentAsString();
                    break;
            }
        }

        /// <summary>
        /// Read all discounts and store them in the newPlayer object.
        /// </summary>
        /// <param name="reader">A subtree that contains only the "discounts" element's content.</param>
        /// <param name="newPlayer">The player object to store the discounts in.</param>
        private static void ReadDiscounts(XmlReader reader, Player newPlayer)
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    StoreDiscount(reader.ReadSubtree(), newPlayer);
                }
            }
        }

        /// <summary>
        /// Store discounts that have been read in from a file to a Player object.
        /// </summary>
        /// <param name="reader">A subtree that contains only a "discount" element's content.</param>
        /// <param name="newPlayer">The player object to store the discount in.</param>
        private static void StoreDiscount(XmlReader reader, Player newPlayer)
        {
            reader.Read();

            if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "id"))
            {
                int id = reader.ReadElementContentAsInt();
                foreach (DiscountType dt in DiscountTypes.AllTypes)
                {
                    if (dt.Id == id)
                    {
                        newPlayer.Discounts.Add(dt);
                    }
                }
            }
        }
    }
}
